Permissions of a permissionless staking pool
Rocket Pool is far more interesting than other smart-contract-based staking pools.
However, to what extent can we consider it a permissionless staking pool?
The closest I found to an answer was this high-level article outlining some of the roles in the system. But it could be outdated (it was published in February 2021) and I wanted a more technical explanation.
So I began digging into Rocket Pool’s contracts. Documenting roles, permissions and powers as I was finding them today on Ethereum mainnet.
What started as a personal list of random notes has matured into this article.
Perhaps this can serve as a starting point for the Rocket Pool community to build and publish “official” technical documentation stating the roles in the system, along with the associated powers, risks and expected behaviors.
The trusted nodes
Trusted nodes are a set of accounts with significant powers and responsibilities in Rocket Pool. They play a critical role to keep it healthy and safe. In return for their services, the system pays them in RPL tokens.
At the moment of writing there are 14 of these trusted nodes. They are all controlled by EOAs. Their addresses, along with public Etherscan tags, are:
0xB3A533098485bede3Cb7fA8711AF84FE0bb1e0aD - Nimbus Oracle DAO
0xCCbFF44E0f0329527feB0167bC8744d7D5aEd3e9 - Rocket Pool: Oracle DAO Node
0xAf820bb236FdE6f084641c74a4C62fA61D10b293 - Etherscan: Oracle DAO Node
0x8d074ad69b55dD57dA2708306f1c200ae1803359 - Rocket Pool: Oracle DAO Node4
0x751683968FD078341C48B90bC657d6bAbc2339F7 - EthStaker: Oracle DAO Node
0x1Df9a4f9421ae6306BC7b171Dff8470640f96e72 - CryptoManufaktur: Oracle DAO Node
0xB13fA6Eff52e6Db8e9F0f1B60B744A9a9A01425A - Blockchain Capital: Oracle DAO node
0x2c6c5809A257ea74a2Df6d20aeE6119196d4bEA0 - Bankless DAO: Oracle DAO Node
0xD7F94c53691AFB5A616C6af96e7075c1FFA1D8eE - beaconcha.in: Oracle DAO Node
0xDf590A63B91E8278a9102BEe9aAfd444F8A4b780 - Consensys: Oracle DAO Node
0x9c69e7FCe961FD837F99c808aD75ED39a2c549Cb - Fire Eyes DAO: Oracle DAO Node
0xc5D291607600044348E5014404cc18394BD1D57d - Lighthouse: Oracle DAO Node
0x2354628919e1D53D2a69cF700Cc53C4093977B94 - no tag
0x16222268bB682AA34cE60C73F4527F30aCA1b788 - no tag
No surprise there are familiar names in the list. These are already mentioned in Rocket Pool’s landing page.
These accounts operate by running an instance of the Rocket Pool Smart Node. The Golang code of Smart Nodes is open source and available at this repository.
Identifying trusted nodes operations
Permissioned functions that only trusted nodes must call are commonly marked with the onlyTrustedNode
modifier. The modifier is defined in the RocketBase
contract.
Though sparsely used, another way of authorizing trusted nodes is using the getMemberIsValid
function of the RocketDAONodeTrusted
contract (deployed here).
With these two authorization mechanisms in mind, one can start mapping most (if not all) of these privileged accounts’ powers. It’s a matter of looking for exposed contract functions that at some point authorize the caller (msg.sender
) using either of them.
Permissioned operations
The set of permissioned operations delegated to trusted nodes can be divided into: management of trusted proposals and actions, submission of network balances, submission of network prices, submission of rewards snapshots, submission of penalties, and minipool management.
Trusted proposals
Trusted nodes are the sole actors of Rocket Pool with the power to submit, vote and cancel proposals on the Oracle DAO (sometimes referred to as oDAO for short). Following a simple voting system, they can reach consensus to change sensitive parameters and contracts of the system.
The main entrypoint for proposals is the RocketDAONodeTrustedProposals
contract (deployed here). This contract is associated with a RocketDAOProposal
contract (deployed here). The latter handles the lower-level reading/writing operations to effectively manage proposals throughout their whole lifecycle.
There are 3 main operations that trusted nodes can perform on the RocketDAONodeTrustedProposals
contract: creation, voting and cancelling of proposals.
Execution is permissionless, and done via the contract’s execute
function. At the moment of writing, a proposal can only be executed after >50% of the trusted nodes have explicitly voted in favor. In numbers: 8 out of 14.
Creating proposals
Done using the propose
function.
The amount of proposals that a trusted node can submit in a given window of time is limited.
A proposal includes arbitrary calldata intended to be executed on the RocketDAONodeTrustedProposals
contract itself, once the proposal in considered approved.
Therefore, in practice, actions are just calls to functions of the RocketDAONodeTrustedProposals
contract. These functions, only available to be called via proposals, can be found marked with the onlyExecutingContracts
modifier. They include:
- Managing members: inviting a new trusted node (calling
proposalInvite
), stopping being a trusted node (callingproposalLeave
) and kicking another trusted node (callingproposalKick
). - Changing sensitive settings, calling the
proposalSettingUint
andproposalSettingBool
functions. These allow easily updating settings that are separated into individual contracts.- In the
RocketDAONodeTrustedSettingsMembers
contract (deployed here), trusted members can change: quorum threshold, amount of RPL a new member needs to bond, amount of unbonded minipool validators permitted and under which circumstances they’re allowed, as well as challenge-related parameters like cooldown, window and cost (more on challenges later). - In the
RocketDAONodeTrustedSettingsProposals
contract (deployed here), trusted members can change: time between subsequent proposals, how long voting period lasts, how long voting delay lasts, how long a proposal can be executed before it becomes stale, how long the expiration time is for a corresponding action once a proposal is successful. - In the
RocketDAONodeTrustedSettingsRewards
contract (deployed here), there’s a single setting available. By changing it, trusted nodes can determine whether a network is available for a regular node to receive rewards or not. Notably, they cannot disable mainnet rewards (assuming mainnet is identified with the internal network ID 0). - In the
RocketDAONodeTrustedSettingsMinipool
contract (deployed here), trusted members can change: required quorum to scrub a minipool, the period of time during which a minipool can be scrubbed, as well as whether scrubbing a minipool results in RPL penalties.
- In the
- Upgrading addresses of contracts in the system (calling the
proposalUpgrade
function), except:- The address of the
RocketVault
contract (currently pointing here). - The address of the
RocketTokenRETH
contract (currently pointing here). - The address of the
RocketTokenRPL
contract (currently pointing here). - The address of the
RocketMinipoolPenalty
contract (currently pointing here). - The address Ethereum validators’ deposit contract (currently pointing here)
- The address of the
Voting proposals
Done using the vote
function.
Voting is the way to express support for a submitted proposal that attempts to execute one of the available actions. Each trusted node can cast one vote per proposal.
Voting is allowed as long as is done within a determined time window. Moreover, the voter must have joined the group of trusted nodes prior to the proposal being created.
Cancelling proposals
Done using the cancel
function.
Only the creator of a proposal can cancel it. Cancelling is only possible if the proposal is pending (voting period hasn’t started) or active (voting period has started but not finished).
Trusted actions
In some cases, an approved proposal requires an additional action from a trusted node. These actions are included in the RocketDAONodeTrustedActions
contract. The most relevant ones are:
- Becoming a trusted node calling the
actionJoin
function. Caller must have been invited via a previous proposal, be a registered node in Rocket Pool, and be ready to deposit a bond of RPL tokens. Querying theRocketDAONodeTrustedSettingsMembers
contract deployed here, one can tell that the bond is 1750 RPL tokens (almost 43k USD at the moment of writing). - Leaving the set of trusted nodes calling the
actionLeave
function. Possible as long as this wouldn’t leave the set of trusted nodes below the minimum required. This action refunds the trusted node their RPL bond. - Kicking another member calling the
actionKick
function.
Furthermore, any trusted node can freely challenge another trusted node. Non-trusted nodes can do it too, but they must pay ETH (so as to prevent spamming). Challenging is done calling the actionChallengeMake
function. If the challenged account does not respond timely to the challenge, they may be removed by any registered node that calls the actionChallengeDecide
function.
Network balances
Rocket Pool requires externally provided balance information to operate efficiently. This information is provided by the trusted nodes, this time acting as oracles for Rocket Pool.
Trusted nodes / oracles are expected to periodically call the submitBalances
function of the RocketNetworkBalances
contract (deployed here) to report:
- Block number from which the information was collected.
- Total amount of ETH in the Rocket Pool network.
- Total amount of ETH staked in PoS validators by Rocket Pool.
- Supply of the rETH token.
In the screenshot below you can see 8 of the trusted nodes submitting the network balances:
Notably, no single actor is entitled to act as the ultimate source of truth for these balances. Instead, network balances are only updated on-chain once a consensus threshold is met. That is, once a number of trusted nodes call the submitBalances
function with the same information.
The current consensus threshold required is >50% of the trusted nodes. The threshold value can be queried from the RocketDAOProtocolSettingsNetwork
contract (deployed here).
Trusted nodes cannot change this threshold. Instead, it is controlled by a guardian account (more on this later).
Network prices
Rocket Pool also needs the current market price of RPL. For example, to determine the minimum and maximum amount of RPL tokens nodes must stake as collateral.
The price of RPL, along with how much RPL is effectively staked, must be submitted periodically by the trusted nodes. The process is similar to the submission of network balances.
Trusted nodes call the submitPrices
function of the RocketNetworkPrices
contract (deployed here) to report:
- Block number from which the information was collected.
- Price of the RPL token.
- Effective amount of RPL staked in the network.
In the screenshot below you can see 8 of the trusted nodes submitting prices:
The RPL price is fetched by each individual trusted node from an on-chain contract. More specifically, their Smart Nodes regularly query a smart contract from 1inch. Then simply submit the price to the Rocket Pool contract.
See this by yourself in the Smart Node service’s code. Here the RPL price is fetched from the 1inch contract at mainnet address 0x07D91f5fb9Bf7798734C3f606dB065549F6893bb
, and a few lines later the price is submitted to the Rocket Pool contract.
Going deeper into how the 1inch oracle builds the price for RPL in ETH, you might notice that the actual price is the result of combining the spot price of RPL in two Uniswap V3 pools of RPL/WETH (this pool and this pool).
Rewards
All regular node operators in Rocket Pool are entitled to frequent rewards of RPL tokens. Reward mechanics are explained in the docs.
The assignment of rewards requires snapshots of on-chain state to be taken regularly. That’s also done by trusted nodes. They make these snapshots available on-chain calling the submitRewardSnapshot
function of the RocketRewardsPool
contract (deployed here).
In the screenshot below you can see 8 of the trusted nodes submitting the rewards snapshots.
While trusted nodes are the only ones who can submit snapshots, in principle there shouldn’t be anyone with enough power to maliciously influence the submitted data. It’s similar to network balances and prices. >50% of the trusted nodes must agree on the same snapshot before it becomes actionable and enables distribution of RPL.
Penalties
Trusted nodes are entitled to penalize regular node operators. They can do it by calling the submitPenalty
function of the RocketNetworkPenalties
contract (deployed here). Trusted nodes must provide:
- Block number for the penalty.
- Address of the node operator’s minipool.
More than 50% of the trusted nodes must agree on a penalty for a minipool before its penalty counter is incremented by one. One the counter is 3 or more, the minipool starts accounting penalty rates.
For more information on the penalty system and what kind of actions trusted nodes must consider a penalty, refer to the docs.
Minipool management
Regarding minipool management, there are two main operations delegated to trusted nodes.
First operation is “scrubbing” a minipool in prelaunch state. According to the docs, trusted nodes should scrub a minipool when its withdrawal credentials are not set correctly. Scrubbing leads to dissolving the minipool, and assigning penalties to the node operator (if these are enabled). It’s done calling the voteScrub
function of RocketMinipoolDelegate
contract, via the corresponding instance of a RocketPool
contract (that will delegatecall to RocketMinipoolDelegate
). Scrubbing has its own quorum setting, currently set to 51%. This value can be queried using the getScrubQuorum
function of the RocketDAONodeTrustedSettingsMinipool
contract (deployed here).
Second operation is moving staking minipools to the withdrawable state. This is done calling the submitMinipoolWithdrawable
function of the RocketMinipoolStatus
contract (deployed here). The consensus threshold of >50% must be met for a minipool to be made withdrawable.
The guardian
Aside from the powers granted to the trusted nodes, Rocket Pool intends to delegate other privileges to a broader governance entity steered by RPL token holders (a.k.a. “The Protocol DAO”). This governance body is still in early stages, and hasn’t been fully activated yet.
The main on-chain contract where the Protocol DAO would operate is the RocketDAOProtocol
(deployed here). By querying its getBootstrapModeDisabled
function, you can verify that the contract is still in "bootstrap mode”.
This means there’s a “guardian” account that holds all powers that Rocket Pool’s Protocol DAO might hold in the future.
The address of the guardian account can be queried using the getGuardian
function of the RocketStorage
contract (deployed here). It returns 0x0ccf14983364a7735d369879603930afe10df21e
at the moment of writing. That’s an EOA.
As long as the RocketDAOProtocol
contract is in bootstrap mode, the guardian is entitled to call any function in the contract that is marked with the onlyGuardian
modifier (in turn defined in the RocketBase
contract).
In short, today the guardian holds absolute powers over several sensitive settings and actions of Rocket Pool.
Let’s see the details.
First, the guardian can arbitrarily change sensitive settings via the RocketDAOProtocol
contract, calling its bootstrapSettingMulti
, bootstrapSettingUint
, bootstrapSettingBool
, bootstrapSettingAddress
and bootstrapSettingClaimer
functions. The specific settings are split into individual contracts for ease of reference.
-
Settings related to auctions for liquidations of RPL tokens
The guardian can enable/disable lot creation, enable/disable bidding on lots, determine the minimum lot size, determine the maximum lot size, determine the number of block an auction lasts, as well as the auctions’ starting and reserve prices. Done in the
RocketDAOProtocolSettingsAuction
contract (deployed here). -
Settings related to ETH deposits and assignments to minipools
The guardian can enable/disable deposits, enable/disable assignments of deposits to minipools, determine the minimum deposit size, determine the maximum size of the deposits pool, determine the maximum number of deposit assignments that can be performed at once, as well as the deposit fee charged to users. Done in the
RocketDAOProtocolSettingsDeposit
contract (deployed here). -
Settings related to the network
The guardian can determine the trusted nodes’ consensus thresholds on oracle data and penalties, determine the penalty rate per minipool in case of infractions, enable/disable submission of network balances, prices and rewards by trusted nodes, determine the expected frequency of balances and prices submissions, determine the minimum, maximum and target commission rates for node operators, determine the range for nodes’ demand to calculate fees, determine the rETH collateralization rate, as well as determine the rETH withdrawal delay (which cannot exceed 5760 blocks). Done in the
RocketDAOProtocolSettingsNetwork
contract (deployed here). -
Settings related to protocol rewards
The guardian can determine the period over which rewards can be claimed, and what contracts are allowed to claim RPL rewards, with the corresponding % of rewards. Done in the
RocketDAOProtocolSettingsRewards
contract (deployed here). -
Settings related to minipools
The guardian can determine the timeout in seconds to launch minipools, the global maximum of minipools allowed, and whether trusted nodes are allowed to move minipools to the withdrawable state. Done in the
RocketDAOProtocolSettingsMinipool
contract (deployed here)
Second, the guardian can set the maximum penalty rate for all minipools in Rocket Pool. Setting the maximum rate to zero would effective disable penalties. Done calling the setMaxPenaltyRate
function of the RocketMinipoolPenalty
contract (deployed here).
Third, the guardian can spend RPL tokens from the treasury. Done calling the bootstrapSpendTreasury
function of the RocketDAOProtocol
contract.
Last, the guardian can resign its powers by disabling the bootstrap mode. This is done calling the bootstrapDisable
function of the RocketDAOProtocol
contract. An action that cannot be undone.
The guardian used to have similar ruling powers on the RocketDAONodeTrusted
contract, which ruled the RocketDAONodeTrustedProposals
contract. At some point, these power were passed to the set of trusted nodes. That’s why today the RocketDAONodeTrusted
contract is no longer in bootstrap mode.
Once Rocket Pool’s Protocol DAO matures, we might see the guardian abdicating its powers on the RocketDAOProtocol
contract, which today rules the RocketDAOProtocolProposals
contract. Finally handing over the reins to a larger community interested in the well-being of the protocol.
Refer to this article for more details on Rocket Pool's governance roadmap.